using System;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.Data;
using System.Transactions   ;
using SubSonicMembershipProvider.DataObjects;
using SubSonic;
namespace SubSonicRoles
{
    public class SimpleRoleProvider : RoleProviderBase
    {
        #region EntityMethods
        protected UserMembership GetUserEntity(string userName)
        {
            UserMembership user = new UserMembership(UserMembership.Columns.LoweredUserName, userName.ToLower());
            if (!user.IsLoaded)
            {
                return null;
            }
            return user;
        }
        protected string[] GetStringArrayFromRoleCollection(RoleCollection roles)
        {
            string[] arrRoles = new string[roles.Count];
            for (int i = 0; i < roles.Count; i++)
            {
                arrRoles[i] = roles[i].RoleName;
            }
            return arrRoles;
        }
        protected string[] GetStringArrayFromUsersCollection(UserMembershipCollection users)
        {
            string[] arrUsers = new string[users.Count];
            for (int i = 0; i < users.Count; i++)
            {
                arrUsers[i] = users[i].UserName;
            }
            return arrUsers;
        }
        protected override int GetApplicationId(string applicationName)
        {
            return 0;
        }
        #endregion


        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            foreach (string roleName in roleNames)
            {
                if (!RoleExists(roleName))
                {
                    throw new ProviderException("Role name not found.");
                }
            }

            foreach (string username in usernames)
            {
                if (username.IndexOf(',') > 0)
                {
                    throw new ArgumentException("User names cannot contain commas.");
                }
                if (username == "")
                {
                    throw new ArgumentException("One or more of the supplied usernames is blank");
                }
                if (username == null)
                {
                    throw new ArgumentNullException("One or more of the supplied usernames is null");
                }
                foreach (string roleName in roleNames)
                {
                    if (IsUserInRole(username, roleName))
                    {
                        throw new ProviderException("User is already in role.");
                    }
                }
            }

            //TODO: create a transaction so that if one insert fails they all fail
            Query usernameQuery = new Query(UserMembership.Schema).IN(UserMembership.Columns.UserName, usernames);
            UserMembershipCollection users = new UserMembershipCollection();
            users.LoadAndCloseReader(usernameQuery.ExecuteReader());

            Query rolesQuery = new Query(Role.Schema).IN(Role.Columns.RoleName, roleNames);
            RoleCollection roles = new RoleCollection();
            roles.LoadAndCloseReader(rolesQuery.ExecuteReader());

            foreach (UserMembership user in users)
            {
                foreach (Role role in roles)
                {
                    UserRole userRole = new UserRole();
                    userRole.UserId = user.UserId;
                    userRole.RoleId = role.RoleId;
                    userRole.Save();
                }
            }
            
        }

        public override void CreateRole(string roleName)
        {
            if (roleName.IndexOf(',') > 0)
            {
                throw new ArgumentException("Role names cannot contain commas.");
            }

            if (RoleExists(roleName))
            {
                throw new ProviderException("Role name already exists.");
            }
            try
            {
                Role role = new Role();
                role.RoleName = roleName;
                role.LoweredRoleName = roleName.ToLower();
                role.Save();
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "CreateRole");
                }
                else
                {
                    throw;
                }
            }
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (!RoleExists(roleName))
            {
                throw new ProviderException("Role does not exist.");
            }

            if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
            {
                throw new ProviderException("Cannot delete a populated role.");
            }

            try
            {
                Role r = new Role(Role.Columns.LoweredRoleName, roleName.ToLower());
                RoleController rc = new RoleController();
                rc.Delete(r.RoleId);
                
                return true;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "DeleteRole");

                    return false;
                }
                else
                {
                    throw;
                }
            }
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            try
            {
                if (!RoleExists(roleName))
                    throw new ProviderException("Specified roleName: " + roleName + " does not exists");

                UserMembershipCollection users = new UserMembershipCollection();
                users.LoadAndCloseReader(SPs.FindUsersInRole(usernameToMatch, roleName).GetReader());
                
                return GetStringArrayFromUsersCollection(users);

            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersInRole");
                    throw;
                }
                else
                {
                    throw;
                }
            }
        }

        public override string[] GetAllRoles()
        {
            try
            {
                RoleCollection roles = new RoleCollection();
                roles.Load();
                return GetStringArrayFromRoleCollection(roles);
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetAllRoles");
                    throw;
                }
                else
                {
                    throw;
                }
            }
        }

        public override string[] GetRolesForUser(string username)
        {
            if (username == "")
                throw new ArgumentException("The provided username is blank");
            if (username == null)
                throw new ArgumentNullException("The provided username is null");


            UserMembership user = new UserMembership(UserMembership.Columns.UserName, username);
            
            if (!user.IsLoaded)
            {
                throw new ArgumentException("The user " + username + " does not exist");
            }
            try
            {
                return GetStringArrayFromRoleCollection(user.GetRoleCollection());
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetRolesForUser");
                    throw;
                }
                else
                {
                    throw;
                }
            }
        }

        public override string[] GetUsersInRole(string roleName)
        {
            if (roleName == "")
                throw new ArgumentException("The provided role name is blank");
            if (roleName == null)
                throw new ArgumentNullException("The provided role name is null");

            Role role = new Role(Role.Columns.RoleName, roleName);
            
            
            if (!role.IsLoaded)
            {
                throw new ProviderException("The role " + roleName + " does not exist");
            }
            try
            {
                return GetStringArrayFromUsersCollection(role.GetUserMembershipCollection());
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUsersInRole");
                    throw;
                }
                else
                {
                    throw;
                }
            }
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            try
            {
                RoleCollection roles = new RoleCollection();
                return Convert.ToBoolean(SPs.IsUserInRole(username, roleName).ExecuteScalar());
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "IsUserInRole");
                    throw;
                }
                else
                {
                    throw;
                }
            }
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            foreach (string roleName in roleNames)
            {
                if (!RoleExists(roleName))
                {
                    throw new ProviderException("Role name not found.");
                }
            }

            foreach (string username in usernames)
            {
                foreach (string roleName in roleNames)
                {
                    if (!IsUserInRole(username, roleName))
                    {
                        throw new ProviderException("User is not in role.");
                    }
                }
            }
            try
            {
                /* this is pretty non-optimal code, should probably adjust it to use a stored proc */
                Query usernameQuery = new Query(UserMembership.Schema).IN(UserMembership.Columns.UserName, usernames);
                UserMembershipCollection users = new UserMembershipCollection();
                users.LoadAndCloseReader(usernameQuery.ExecuteReader());

                Query rolesQuery = new Query(Role.Schema).IN(Role.Columns.RoleName, roleNames);
                RoleCollection roles = new RoleCollection();
                roles.LoadAndCloseReader(rolesQuery.ExecuteReader());

                foreach (Role role in roles)
                {
                    foreach (UserMembership user in users)
                    {
                        Query q = new Query(UserRole.Schema).WHERE(UserRole.Columns.UserId, user.UserId).AND(UserRole.Columns.RoleId, role.RoleId);
                        UserRoleController urc = new UserRoleController();
                        
                        UserRole userRole = new UserRole();
                        userRole.LoadAndCloseReader(q.ExecuteReader());

                        UserRole.Delete(userRole.UserRoleId);
                    }  
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "RemoveUsersFromRoles");
                }
                else
                {
                    throw;
                }
            }
        }

        public override bool RoleExists(string roleName)
        {
            try
            {
                Role role = new Role(Role.Columns.RoleName, roleName);
                return role.IsLoaded;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "RoleExists");
                    throw;
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
